Basic Data Management

Setting the R Packages to be used

library(zoo) #Date manipulation package
library(smooth) #For Exponential Smoothing
library(greybox)
library(dplyr)
library(tidyr)
library(leaflet)
library(gencve)
library(plotly)
library(forecast)
library(tseries)
library(x13binary)
library(seasonalview)

Setting the Data

setwd("E:/College Notes/GE-FEL DWIADS/Time Series")
#Import Data and Manage Date
Reg8 <-read.csv("Final Regional Data.csv", header=T)
head(Reg8)

Here, one can see the working data that will be used for the analyses and forecasting. The data was obtained from the DOH COVID-19 Tracker website. Description of the Data:

  • ï..Week corresponds to the week dates seen in the DOH COVID-19 Tracker page;
  • Wname is a simplified identifier for the data points;
  • Confirmed, Death, and Recovered are the numerical data that will be used in the analyses;
  • Provres specifies the region to be studied
  • The Latitude and Longitude column specifies the location of the regional center (can be used in mapping the cases in a map).

Visualizing the Cases Using Plotly

NReg8<-Reg8[-c(1,64),]
NReg8<-NReg8%>%
  mutate(Active = Confirmed - Death - Recovered) %>%
  mutate(Active_total = cumsum(Active),
         Recovered_total = cumsum(Recovered),
         Death_total = cumsum(Death))
NReg8$Wname<-factor(NReg8$Wname,levels=NReg8$Wname)

Note: The data for the visualization of the Moving Average was obtained through the rollmean function of the zoo R package. It has been checked and confirmed to be in agreement with the moving average found in the DOH website.

Confirmed Cases

NCma<-zoo::rollmean(NReg8$Confirmed,k=4,fill=NA)
NReg8%>%
plot_ly(x = ~ Wname,
        y = ~ Confirmed,
        name = 'Confirmed', 
        marker=list(color = '#4682b4'),
        type = 'bar') %>%
  add_trace(x = ~ Wname,
            y = ~ NCma,
            type = 'scatter',
            name = '4-Week Moving Average',
            mode='lines',
            line=list(color='#FFA500'),
            marker=list(color='#FFA500'),
            hoverinfo="text",
            text=~paste("4-Week Moving Average:",NCma))%>%
  layout(title = "Confirmed COVID-19 Cases - Eastern Visayas",
         legend = list(x = 0.1, y = 0.9),
         yaxis = list(title = "Number of Cases"),
         xaxis = list(title = "Source: <a href='https://doh.gov.ph/covid19tracker'>DOH COVID-19 Tracker</a>",tickangle=45))

Deceased Cases

NDma<-zoo::rollmean(NReg8$Death,k=4,fill=NA)
NReg8%>%
plot_ly(x = ~ Wname,
        y = ~ Death,
        name = 'Death', 
        marker=list(color = '#ff0000'),
        type = 'bar')%>%
  add_trace(x = ~ Wname,
            y = ~ NDma,
            type = 'scatter',
            name = '4-Week Moving Average',
            mode='lines',
            line=list(color='#FFA500'),
            marker=list(color='#FFA500'),
            hoverinfo="text",
            text=~paste("4-Week Moving Average:",NDma))%>%
  layout(title = "COVID-19 Deaths - Eastern Visayas",
         legend = list(x = 0.1, y = 0.9),
         yaxis = list(title = "Number of Cases"),
         xaxis = list(title = "Source: <a href='https://doh.gov.ph/covid19tracker'>DOH COVID-19 Tracker</a>",tickangle=45))

Recovered Cases

NRma<-zoo::rollmean(NReg8$Recovered,k=4,fill=NA)
NReg8%>%
plot_ly(x = ~ Wname,
        y = ~ Recovered,
        name = 'Recovered', 
        marker=list(color = '#3cb371'),
        type = 'bar') %>%
  add_trace(x = ~ Wname,
            y = ~ NRma,
            type = 'scatter',
            name = '4-Week Moving Average',
            mode='lines',
            line=list(color='#FFA500'),
            marker=list(color='#FFA500'),
            hoverinfo="text",
            text=~paste("4-Week Moving Average:",NRma))%>%
  layout(title = "COVID-19 Recoveries - Eastern Visayas",
         legend = list(x = 0.1, y = 0.9),
         yaxis = list(title = "Number of Cases"),
         xaxis = list(title = "Source: <a href='https://doh.gov.ph/covid19tracker'>DOH COVID-19 Tracker</a>",tickangle=45))

Plotting Recoveries vs. Death: Can We Possibly Survive COVID-19?

NReg8%>%
  plot_ly(x = ~ Wname,
          y = ~ Death,
          name = 'Death', 
          marker=list(color = '#ff0000'),
          type = 'bar')%>%
  add_trace(x = ~ Wname,
            y = ~ Recovered,
            name = 'Recovered', 
            marker=list(color = '#3cb371'),
            type = 'bar')%>%
  layout(title = "COVID-19 Case Turnout - Eastern Visayas",
         legend = list(x = 0.1, y = 0.9),
         yaxis = list(title = "Number of Cases"),
         xaxis = list(title = "Source: <a href='https://doh.gov.ph/covid19tracker'>DOH COVID-19 Tracker</a>",tickangle=45),
         barmode='stack')

Forecasting with ETS

Forecasting the Confirmed Cases Using ETS Smoothing

ModReg8C.ts<-ts(Reg8$Confirmed[2:63],frequency=52,start=c(2020,3,5))
ModReg8C.ts
Time Series:
Start = c(2020, 3) 
End = c(2021, 12) 
Frequency = 52 
 [1]  26  13   4   1   2  31   4   3   5  53   7   5  12  96 252 131 102  20  79 121 170 239 328 434 665 455 279 319 286 288
[31] 384 478 523 321 432 484 380 334 272 350 532 730 535 555 724 562 469 477 321 242 265 220 282 182 202 336 281 268 308 486
[61] 431 195

Exponential Smoothing (Forecast Horizon: 12)

#Exponential Smoothing Using the smooth Package
ModReg8C.ses<-es(ModReg8C.ts,model="ANN",h=12,holdout=FALSE,intervals="parametric",
                level=0.95,cfType="MSE")
ModReg8C.des<-es(ModReg8C.ts,model="AAdN",h=12,holdout=FALSE,intervals="parametric",
                level=0.95,cfType="MSE")
ModReg8C.hwn<-es(ModReg8C.ts,model="AAN",h=12,holdout=FALSE,intervals="parametric",
                level=0.95,cfType="MSE")
ModReg8C.hwa<-es(ModReg8C.ts,model="AAA",h=12,holdout=FALSE,intervals="parametric",
                level=0.95,cfType="MSE")
Sorry, but we don't have enough observations for the seasonal model!
Switching to non-seasonal.
ModReg8C.hwm<-es(ModReg8C.ts,model="AAM",h=12,holdout=FALSE,intervals="parametric",
                level=0.95,cfType="MSE")
Sorry, but we don't have enough observations for the seasonal model!
Switching to non-seasonal.
ModReg8C.auto<-es(ModReg8C.ts,model="ZZZ",h=12,holdout=FALSE,intervals="parametric",
                 level=0.95,cfType="MSE")
Sorry, but we don't have enough observations for the seasonal model!
Switching to non-seasonal.

Seasonal models cannot be used due to the lack of observations (data size is just 60+, compared to the backdrop of 52 weeks per year). Therefore, AAA and AAM will not be used. We are left with ANN, AAdN and AAN models.

#ANN means Additive errors, No trend, No seasonality (SES)
ModReg8C.ses
Time elapsed: 0.04 seconds
Model estimated: ETS(ANN)
Persistence vector g:
alpha 
    1 
Initial values were optimised.

Loss function type: likelihood; Loss function value: 375.2523
Error standard deviation: 104.5759
Sample size: 62
Number of estimated parameters: 2
Number of provided parameters: 1
Number of degrees of freedom: 60
Information criteria:
     AIC     AICc      BIC     BICc 
754.5046 754.7080 758.7589 759.1786 
#AAdN means Additive errors, Additive (dampled) trend, No seasonality (DES)
ModReg8C.des
Time elapsed: 0.04 seconds
Model estimated: ETS(AAdN)
Persistence vector g:
alpha  beta 
    1     0 
Damping parameter: 0.9647
Initial values were optimised.

Loss function type: likelihood; Loss function value: 375.1587
Error standard deviation: 106.2032
Sample size: 62
Number of estimated parameters: 4
Number of provided parameters: 2
Number of degrees of freedom: 58
Information criteria:
     AIC     AICc      BIC     BICc 
758.3174 759.0192 766.8260 768.2741 
#AAN means Additive errors, Additive trend, No seasonality (HW no seasonality)
ModReg8C.hwn
Time elapsed: 0.03 seconds
Model estimated: ETS(AAN)
Persistence vector g:
alpha  beta 
    1     0 
Initial values were optimised.

Loss function type: likelihood; Loss function value: 375.2302
Error standard deviation: 105.4208
Sample size: 62
Number of estimated parameters: 3
Number of provided parameters: 2
Number of degrees of freedom: 59
Information criteria:
     AIC     AICc      BIC     BICc 
756.4604 756.8742 762.8418 763.6957 
#ZZZ means all components are (Z) estimated 21
ModReg8C.auto
Time elapsed: 0.14 seconds
Model estimated: ETS(ANN)
Persistence vector g:
alpha 
    1 
Initial values were optimised.

Loss function type: likelihood; Loss function value: 375.2523
Error standard deviation: 104.5759
Sample size: 62
Number of estimated parameters: 2
Number of provided parameters: 1
Number of degrees of freedom: 60
Information criteria:
     AIC     AICc      BIC     BICc 
754.5046 754.7080 758.7589 759.1786 

It can be seen above that the ETS(ANN) model produces the least Akaike Information Criterion (AIC). It can also be seen that for ModReg8C.auto, its function returned an ETS(ANN) model.

Checking Forecast Accuracy using Mean Absolute Percentage Error (MAPE)

We will choose the best smoothing model by checking the lowest value.

MAPE(ModReg8C.ts,ModReg8C.ses$fitted)
[1] 0.6918303
MAPE(ModReg8C.ts,ModReg8C.des$fitted)
[1] 1.138553
MAPE(ModReg8C.ts,ModReg8C.hwn$fitted)
[1] 0.7899619
MAPE(ModReg8C.ts,ModReg8C.auto$fitted)
[1] 0.6918303

From the results, the Additive, No trend, No seasonality (ANN) model will be used.

Forecasting 4 Weeks Using the Model with the Lowest MAPE

forecast(ModReg8C.auto,h=4)
Time Series:
Start = c(2021, 13) 
End = c(2021, 16) 
Frequency = 52 
         Point forecast Lower bound (2.5%) Upper bound (97.5%)
2021.231            195          -12.39130            402.3913
2021.250            195          -98.29558            488.2956
2021.269            195         -164.21226            554.2123
2021.288            195         -219.78259            609.7826
plot(forecast(ModReg8C.auto,h=4))

Forecasting 8 Weeks

forecast(ModReg8C.auto,h=8)
Time Series:
Start = c(2021, 13) 
End = c(2021, 20) 
Frequency = 52 
         Point forecast Lower bound (2.5%) Upper bound (97.5%)
2021.231            195          -12.39130            402.3913
2021.250            195          -98.29558            488.2956
2021.269            195         -164.21226            554.2123
2021.288            195         -219.78259            609.7826
2021.308            195         -268.74104            658.7410
2021.327            195         -313.00285            703.0029
2021.346            195         -353.70579            743.7058
2021.365            195         -391.59117            781.5912
plot(forecast(ModReg8C.auto,h=8))

From the results, a flat horizontal line forecast is predicted. The horizontal line forecast may be caused due to backlogs and missing data. According to an article in Wolters Kluwer, “in the absence of real patterns, the best forecast might just be a straight line…. The error on a straight-line forecast may be higher than the error you see on other forecasts; not because the forecast was poor, but because the historical data is truly random.” For Chase, 2019, flat-line forecasts are generated when a given demand history/data has no detectable trend or seasonality, thus just reflecting the current demand level.

Making an ETS Using the Forecast Library

plot.ts(Reg8$Confirmed[2:63],
        ylab="Confirmed",
        xlab="Date")

datac.model <-ets(ts(Reg8$Confirmed[2:63],frequency=52,start=c(2020,3,11)),model="ZNN")
forecast(datac.model,h=30)
plot(forecast(datac.model,h=30),
     main="Forecast on the Number of COVID-19 Cases in Region VIII",
     col="red",
     xlab="Date",
     ylab="Number of Cases")

Forecasting with ARIMA

Forecasting the Confirmed Cases Using ARIMA

Augmented Dickey-Fuller Test (ADF) to Determine the d-value

adf.test(ModReg8C.ts, alternative="stationary")

    Augmented Dickey-Fuller Test

data:  ModReg8C.ts
Dickey-Fuller = -1.9935, Lag order = 3, p-value = 0.5775
alternative hypothesis: stationary
adf.test(diff(ModReg8C.ts), alternative="stationary")
p-value smaller than printed p-value

    Augmented Dickey-Fuller Test

data:  diff(ModReg8C.ts)
Dickey-Fuller = -4.4387, Lag order = 3, p-value = 0.01
alternative hypothesis: stationary

The ADF Test result for ModReg8C.ts suggests non-stationarity while the result for diff(ModReg8C.ts) suggests stationarity, hinting that d=1. This is due to the p-value for ModReg8C.ts being higher than \(\alpha = 0.05\) while the p-value for diff(ModReg8C.ts) is lower than \(\alpha\), suggesting the rejection of the null hypothesis of unit root presence (stationarity).

Visualizing ACF and Partial ACF

plot(ModReg8C.ts)

acf(ModReg8C.ts)

pacf(ModReg8C.ts)

plot(diff(ModReg8C.ts))

acf(diff(ModReg8C.ts))

pacf(diff(ModReg8C.ts))

Creating an ARIMA Model Manually using arima()

conf.arima <- arima(ModReg8C.ts,order=c(0,1,1))
conf.arima

Call:
arima(x = ModReg8C.ts, order = c(0, 1, 1))

Coefficients:
         ma1
      0.0343
s.e.  0.2238

sigma^2 estimated as 10752:  log likelihood = -369.68,  aic = 743.37
conf.pred <- forecast(conf.arima, h = 48, level=c(97.5))
conf.pred
plot(conf.pred)

Automatic ARIMA Model Determination using auto.arima()

conf.aarima<-auto.arima(ModReg8C.ts,max.order = 12,trace=TRUE,seasonal=FALSE)

 Fitting models using approximations to speed things up...

 ARIMA(2,1,2)            with drift         : 736.263
 ARIMA(0,1,0)            with drift         : 734.2717
 ARIMA(1,1,0)            with drift         : 737.4646
 ARIMA(0,1,1)            with drift         : 736.4677
 ARIMA(0,1,0)                               : 732.1761
 ARIMA(1,1,1)            with drift         : 736.3936

 Now re-fitting the best model(s) without approximations...

 ARIMA(0,1,0)                               : 741.4594

 Best model: ARIMA(0,1,0)                               
conf.aarima
Series: ModReg8C.ts 
ARIMA(0,1,0) 

sigma^2 estimated as 10757:  log likelihood=-369.7
AIC=741.39   AICc=741.46   BIC=743.5

Specifying the Information Criterion (IC) in auto.arima to find p,d, and q

autoaic <- auto.arima(Reg8$Confirmed[2:63],ic='aic')
autoaic
Series: Reg8$Confirmed[2:63] 
ARIMA(0,1,0) 

sigma^2 estimated as 10757:  log likelihood=-369.7
AIC=741.39   AICc=741.46   BIC=743.5
autobic <- auto.arima(Reg8$Confirmed[2:63],ic='bic')
autobic
Series: Reg8$Confirmed[2:63] 
ARIMA(0,1,0) 

sigma^2 estimated as 10757:  log likelihood=-369.7
AIC=741.39   AICc=741.46   BIC=743.5
autoaicc <- auto.arima(Reg8$Confirmed[2:63],ic='aicc')
autoaicc
Series: Reg8$Confirmed[2:63] 
ARIMA(0,1,0) 

sigma^2 estimated as 10757:  log likelihood=-369.7
AIC=741.39   AICc=741.46   BIC=743.5

All three ICs suggests a p,d,q order of 0,1,0, which is similar to the auto.arima-suggested model without specifying the IC.

The second method automatically checks the ARIMA with the lowest AIC, BIC, and AICC respectively. All agreed that the lowest was ARIMA(0,1,0) which also coincides with the result of the first method.

conf.pred <- forecast(conf.aarima, h = 48, level=c(97.5))
conf.pred
plot(conf.pred)

Here, we see the ARIMA ETS graph and forecast using ARIMA(0,1,0). Similar to the forecast earlier from exponential smoothing , a horizontal line forecast is seen.

Creating a Simple Map Plot Using the Leaflet R Package

In creating a simple map plot, one can use the Leaflet package. Leaflet is an “open-source, JavaScript library for interactive maps” [https://rstudio.github.io/leaflet/]. Leaflet can create maps that can be used directly from the R console, RStudio, and R Markdown documents [https://cran.r-project.org/web/packages/leaflet/index.html]. The package has the capability to map polygons, GeoJSON; create map layers, add map labels, and many more.

library(leaflet)

Here, Leaflet is used to create a simple map plot for the COVID-19 Statistics of Eastern Visayas (Region VIII). To plot the borders of the region, one can manually enter the lengths of the border to create a polygon, or download a shapefile/GeoJSON/TopoJSON containing the specifications of the region’s border lengths. In our case, we used a GeoJSON file obtained from OpenStreetMap. The OSM Relation ID for Eastern Visayas is 3759193.

The codes used below were guided by the tutorials/guides from Earth Lab, an RStudio Pubs guide by Matt Dray, a guide by Jeremy Oakley and from the official RStudio guide by the creators of Leaflet.

labR8<-paste(sep="</br>",
           "<b>Eastern Visayas COVID-19 Statistics:</b>",
           "(As of May 13,2021)",
           paste0("Confirmed Cases:",sum(Reg8$Confirmed)),
           paste0("Active Cases:",NReg8$Active_total+Reg8$Confirmed[64]),
           paste0("Deaths:",NReg8$Death_total),
           paste0("Recovered:",NReg8$Recovered_total))

ntopo<-rgdal::readOGR("EVisayas.geojson")
OGR data source with driver: GeoJSON 
Source: "E:\College Notes\GE-FEL DWIADS\Time Series\EVisayas.geojson", layer: "EVisayas"
with 1 features
It has 0 fields
leaflet(ntopo, width='100%')%>%
  addTiles()%>%
  addPolygons(stroke=TRUE,
              smoothFactor = 0.3,
              fillOpacity = 0.005)%>%
  addPopups(lng=125,lat=11.24,labR8,
            options=popupOptions(closeButton = FALSE,
                                 attribution="From <a href='https://doh.gov.ph/covid19tracker'>DOH COVID-19 Tracker</a>",
                                 closeOnClick = FALSE))
LS0tDQp0aXRsZTogIkNPVklELTE5IFNpdHVhdGlvbjogRWFzdGVybiBWaXNheWFzIChSZWdpb24gVklJSSkiDQpzdWJ0aXRsZTogIkEgVGltZSBTZXJpZXMgQW5hbHlzaXMgYW5kIEZvcmVjYXN0aW5nIEFjdGl2aXR5IGZvciBHRS1GRUwgRFdJQURTIDxicj4gQ3JlYXRlZCBVc2luZyBSIE1hcmtkb3duIg0KYXV0aG9yOiAiRGluZ2xhc2EsIEphYm9uaWxsbywgTWFqYW4sIGFuZCBMaW5ndWFqZSAoR3JvdXAgMikiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KLS0tDQojIyBCYXNpYyBEYXRhIE1hbmFnZW1lbnQNCg0KIyMjIFNldHRpbmcgdGhlIFIgUGFja2FnZXMgdG8gYmUgdXNlZA0KYGBge3Igd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoem9vKSAjRGF0ZSBtYW5pcHVsYXRpb24gcGFja2FnZQ0KbGlicmFyeShzbW9vdGgpICNGb3IgRXhwb25lbnRpYWwgU21vb3RoaW5nDQpsaWJyYXJ5KGdyZXlib3gpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZ2VuY3ZlKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGZvcmVjYXN0KQ0KbGlicmFyeSh0c2VyaWVzKQ0KbGlicmFyeSh4MTNiaW5hcnkpDQpsaWJyYXJ5KHNlYXNvbmFsdmlldykNCmBgYA0KDQojIyMgU2V0dGluZyB0aGUgRGF0YQ0KYGBge3J9DQpzZXR3ZCgiRTovQ29sbGVnZSBOb3Rlcy9HRS1GRUwgRFdJQURTL1RpbWUgU2VyaWVzIikNCiNJbXBvcnQgRGF0YSBhbmQgTWFuYWdlIERhdGUNClJlZzggPC1yZWFkLmNzdigiRmluYWwgUmVnaW9uYWwgRGF0YS5jc3YiLCBoZWFkZXI9VCkNCmhlYWQoUmVnOCkNCmBgYA0KSGVyZSwgb25lIGNhbiBzZWUgdGhlIHdvcmtpbmcgZGF0YSB0aGF0IHdpbGwgYmUgdXNlZCBmb3IgdGhlIGFuYWx5c2VzIGFuZCBmb3JlY2FzdGluZy4gVGhlIGRhdGEgd2FzIG9idGFpbmVkIGZyb20gdGhlIDxhIGhyZWY9J2h0dHBzOi8vZG9oLmdvdi5waC9jb3ZpZDE5dHJhY2tlcic+RE9IIENPVklELTE5IFRyYWNrZXI8L2E+IHdlYnNpdGUuDQpEZXNjcmlwdGlvbiBvZiB0aGUgRGF0YToNCg0KKiBgw68uLldlZWtgIGNvcnJlc3BvbmRzIHRvIHRoZSB3ZWVrIGRhdGVzIHNlZW4gaW4gdGhlIERPSCBDT1ZJRC0xOSBUcmFja2VyIHBhZ2U7DQoqIGBXbmFtZWAgaXMgYSBzaW1wbGlmaWVkIGlkZW50aWZpZXIgZm9yIHRoZSBkYXRhIHBvaW50czsgDQoqIGBDb25maXJtZWRgLCBgRGVhdGhgLCBhbmQgYFJlY292ZXJlZGAgYXJlIHRoZSBudW1lcmljYWwgZGF0YSB0aGF0IHdpbGwgYmUgdXNlZCBpbiB0aGUgYW5hbHlzZXM7DQoqIGBQcm92cmVzYCBzcGVjaWZpZXMgdGhlIHJlZ2lvbiB0byBiZSBzdHVkaWVkIA0KKiBUaGUgYExhdGl0dWRlYCBhbmQgYExvbmdpdHVkZWAgY29sdW1uIHNwZWNpZmllcyB0aGUgbG9jYXRpb24gb2YgdGhlIHJlZ2lvbmFsIGNlbnRlciAoY2FuIGJlIHVzZWQgaW4gbWFwcGluZyB0aGUgY2FzZXMgaW4gYSBtYXApLg0KDQojIyMgVmlzdWFsaXppbmcgdGhlIENhc2VzIFVzaW5nIFBsb3RseQ0KYGBge3J9DQpOUmVnODwtUmVnOFstYygxLDY0KSxdDQpOUmVnODwtTlJlZzglPiUNCiAgbXV0YXRlKEFjdGl2ZSA9IENvbmZpcm1lZCAtIERlYXRoIC0gUmVjb3ZlcmVkKSAlPiUNCiAgbXV0YXRlKEFjdGl2ZV90b3RhbCA9IGN1bXN1bShBY3RpdmUpLA0KICAgICAgICAgUmVjb3ZlcmVkX3RvdGFsID0gY3Vtc3VtKFJlY292ZXJlZCksDQogICAgICAgICBEZWF0aF90b3RhbCA9IGN1bXN1bShEZWF0aCkpDQpOUmVnOCRXbmFtZTwtZmFjdG9yKE5SZWc4JFduYW1lLGxldmVscz1OUmVnOCRXbmFtZSkNCmBgYA0KKk5vdGU6IFRoZSBkYXRhIGZvciB0aGUgdmlzdWFsaXphdGlvbiBvZiB0aGUgTW92aW5nIEF2ZXJhZ2Ugd2FzIG9idGFpbmVkIHRocm91Z2ggdGhlIGByb2xsbWVhbmAgZnVuY3Rpb24gb2YgdGhlIGB6b29gIFIgcGFja2FnZS4gSXQgaGFzIGJlZW4gY2hlY2tlZCBhbmQgY29uZmlybWVkIHRvIGJlIGluIGFncmVlbWVudCB3aXRoIHRoZSBtb3ZpbmcgYXZlcmFnZSBmb3VuZCBpbiB0aGUgRE9IIHdlYnNpdGUuKg0KDQojIyMjIENvbmZpcm1lZCBDYXNlcw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgZmlnLndpZHRoPTksZmlnLmhlaWdodD01LGZpZy5hbGlnbj0nY2VudGVyJ30NCk5DbWE8LXpvbzo6cm9sbG1lYW4oTlJlZzgkQ29uZmlybWVkLGs9NCxmaWxsPU5BKQ0KTlJlZzglPiUNCnBsb3RfbHkoeCA9IH4gV25hbWUsDQogICAgICAgIHkgPSB+IENvbmZpcm1lZCwNCiAgICAgICAgbmFtZSA9ICdDb25maXJtZWQnLCANCiAgICAgICAgbWFya2VyPWxpc3QoY29sb3IgPSAnIzQ2ODJiNCcpLA0KICAgICAgICB0eXBlID0gJ2JhcicpICU+JQ0KICBhZGRfdHJhY2UoeCA9IH4gV25hbWUsDQogICAgICAgICAgICB5ID0gfiBOQ21hLA0KICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywNCiAgICAgICAgICAgIG5hbWUgPSAnNC1XZWVrIE1vdmluZyBBdmVyYWdlJywNCiAgICAgICAgICAgIG1vZGU9J2xpbmVzJywNCiAgICAgICAgICAgIGxpbmU9bGlzdChjb2xvcj0nI0ZGQTUwMCcpLA0KICAgICAgICAgICAgbWFya2VyPWxpc3QoY29sb3I9JyNGRkE1MDAnKSwNCiAgICAgICAgICAgIGhvdmVyaW5mbz0idGV4dCIsDQogICAgICAgICAgICB0ZXh0PX5wYXN0ZSgiNC1XZWVrIE1vdmluZyBBdmVyYWdlOiIsTkNtYSkpJT4lDQogIGxheW91dCh0aXRsZSA9ICJDb25maXJtZWQgQ09WSUQtMTkgQ2FzZXMgLSBFYXN0ZXJuIFZpc2F5YXMiLA0KICAgICAgICAgbGVnZW5kID0gbGlzdCh4ID0gMC4xLCB5ID0gMC45KSwNCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJOdW1iZXIgb2YgQ2FzZXMiKSwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJTb3VyY2U6IDxhIGhyZWY9J2h0dHBzOi8vZG9oLmdvdi5waC9jb3ZpZDE5dHJhY2tlcic+RE9IIENPVklELTE5IFRyYWNrZXI8L2E+Iix0aWNrYW5nbGU9NDUpKQ0KYGBgDQojIyMjIERlY2Vhc2VkIENhc2VzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCBmaWcud2lkdGg9OSxmaWcuaGVpZ2h0PTUsZmlnLmFsaWduPSdjZW50ZXInfQ0KTkRtYTwtem9vOjpyb2xsbWVhbihOUmVnOCREZWF0aCxrPTQsZmlsbD1OQSkNCk5SZWc4JT4lDQpwbG90X2x5KHggPSB+IFduYW1lLA0KICAgICAgICB5ID0gfiBEZWF0aCwNCiAgICAgICAgbmFtZSA9ICdEZWF0aCcsIA0KICAgICAgICBtYXJrZXI9bGlzdChjb2xvciA9ICcjZmYwMDAwJyksDQogICAgICAgIHR5cGUgPSAnYmFyJyklPiUNCiAgYWRkX3RyYWNlKHggPSB+IFduYW1lLA0KICAgICAgICAgICAgeSA9IH4gTkRtYSwNCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsDQogICAgICAgICAgICBuYW1lID0gJzQtV2VlayBNb3ZpbmcgQXZlcmFnZScsDQogICAgICAgICAgICBtb2RlPSdsaW5lcycsDQogICAgICAgICAgICBsaW5lPWxpc3QoY29sb3I9JyNGRkE1MDAnKSwNCiAgICAgICAgICAgIG1hcmtlcj1saXN0KGNvbG9yPScjRkZBNTAwJyksDQogICAgICAgICAgICBob3ZlcmluZm89InRleHQiLA0KICAgICAgICAgICAgdGV4dD1+cGFzdGUoIjQtV2VlayBNb3ZpbmcgQXZlcmFnZToiLE5EbWEpKSU+JQ0KICBsYXlvdXQodGl0bGUgPSAiQ09WSUQtMTkgRGVhdGhzIC0gRWFzdGVybiBWaXNheWFzIiwNCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QoeCA9IDAuMSwgeSA9IDAuOSksDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTnVtYmVyIG9mIENhc2VzIiksDQogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiU291cmNlOiA8YSBocmVmPSdodHRwczovL2RvaC5nb3YucGgvY292aWQxOXRyYWNrZXInPkRPSCBDT1ZJRC0xOSBUcmFja2VyPC9hPiIsdGlja2FuZ2xlPTQ1KSkNCmBgYA0KIyMjIyBSZWNvdmVyZWQgQ2FzZXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsZmlnLndpZHRoPTksZmlnLmhlaWdodD01LGZpZy5hbGlnbj0nY2VudGVyJ30NCk5SbWE8LXpvbzo6cm9sbG1lYW4oTlJlZzgkUmVjb3ZlcmVkLGs9NCxmaWxsPU5BKQ0KTlJlZzglPiUNCnBsb3RfbHkoeCA9IH4gV25hbWUsDQogICAgICAgIHkgPSB+IFJlY292ZXJlZCwNCiAgICAgICAgbmFtZSA9ICdSZWNvdmVyZWQnLCANCiAgICAgICAgbWFya2VyPWxpc3QoY29sb3IgPSAnIzNjYjM3MScpLA0KICAgICAgICB0eXBlID0gJ2JhcicpICU+JQ0KICBhZGRfdHJhY2UoeCA9IH4gV25hbWUsDQogICAgICAgICAgICB5ID0gfiBOUm1hLA0KICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywNCiAgICAgICAgICAgIG5hbWUgPSAnNC1XZWVrIE1vdmluZyBBdmVyYWdlJywNCiAgICAgICAgICAgIG1vZGU9J2xpbmVzJywNCiAgICAgICAgICAgIGxpbmU9bGlzdChjb2xvcj0nI0ZGQTUwMCcpLA0KICAgICAgICAgICAgbWFya2VyPWxpc3QoY29sb3I9JyNGRkE1MDAnKSwNCiAgICAgICAgICAgIGhvdmVyaW5mbz0idGV4dCIsDQogICAgICAgICAgICB0ZXh0PX5wYXN0ZSgiNC1XZWVrIE1vdmluZyBBdmVyYWdlOiIsTlJtYSkpJT4lDQogIGxheW91dCh0aXRsZSA9ICJDT1ZJRC0xOSBSZWNvdmVyaWVzIC0gRWFzdGVybiBWaXNheWFzIiwNCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QoeCA9IDAuMSwgeSA9IDAuOSksDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTnVtYmVyIG9mIENhc2VzIiksDQogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiU291cmNlOiA8YSBocmVmPSdodHRwczovL2RvaC5nb3YucGgvY292aWQxOXRyYWNrZXInPkRPSCBDT1ZJRC0xOSBUcmFja2VyPC9hPiIsdGlja2FuZ2xlPTQ1KSkNCmBgYA0KIyMjIyBQbG90dGluZyBSZWNvdmVyaWVzIHZzLiBEZWF0aDogQ2FuIFdlIFBvc3NpYmx5IFN1cnZpdmUgQ09WSUQtMTk/DQpgYGB7ciBmaWcud2lkdGg9OSxmaWcuaGVpZ2h0PTUsZmlnLmFsaWduPSdjZW50ZXInfQ0KTlJlZzglPiUNCiAgcGxvdF9seSh4ID0gfiBXbmFtZSwNCiAgICAgICAgICB5ID0gfiBEZWF0aCwNCiAgICAgICAgICBuYW1lID0gJ0RlYXRoJywgDQogICAgICAgICAgbWFya2VyPWxpc3QoY29sb3IgPSAnI2ZmMDAwMCcpLA0KICAgICAgICAgIHR5cGUgPSAnYmFyJyklPiUNCiAgYWRkX3RyYWNlKHggPSB+IFduYW1lLA0KICAgICAgICAgICAgeSA9IH4gUmVjb3ZlcmVkLA0KICAgICAgICAgICAgbmFtZSA9ICdSZWNvdmVyZWQnLCANCiAgICAgICAgICAgIG1hcmtlcj1saXN0KGNvbG9yID0gJyMzY2IzNzEnKSwNCiAgICAgICAgICAgIHR5cGUgPSAnYmFyJyklPiUNCiAgbGF5b3V0KHRpdGxlID0gIkNPVklELTE5IENhc2UgVHVybm91dCAtIEVhc3Rlcm4gVmlzYXlhcyIsDQogICAgICAgICBsZWdlbmQgPSBsaXN0KHggPSAwLjEsIHkgPSAwLjkpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIk51bWJlciBvZiBDYXNlcyIpLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlNvdXJjZTogPGEgaHJlZj0naHR0cHM6Ly9kb2guZ292LnBoL2NvdmlkMTl0cmFja2VyJz5ET0ggQ09WSUQtMTkgVHJhY2tlcjwvYT4iLHRpY2thbmdsZT00NSksDQogICAgICAgICBiYXJtb2RlPSdzdGFjaycpDQpgYGANCiMjIEZvcmVjYXN0aW5nIHdpdGggRVRTDQoNCiMjIyBGb3JlY2FzdGluZyB0aGUgQ29uZmlybWVkIENhc2VzIFVzaW5nIEVUUyBTbW9vdGhpbmcNCmBgYHtyfQ0KTW9kUmVnOEMudHM8LXRzKFJlZzgkQ29uZmlybWVkWzI6NjNdLGZyZXF1ZW5jeT01MixzdGFydD1jKDIwMjAsMyw1KSkNCk1vZFJlZzhDLnRzDQpgYGANCg0KIyMjIyBFeHBvbmVudGlhbCBTbW9vdGhpbmcgKEZvcmVjYXN0IEhvcml6b246IDEyKQ0KYGBge3J9DQojRXhwb25lbnRpYWwgU21vb3RoaW5nIFVzaW5nIHRoZSBzbW9vdGggUGFja2FnZQ0KTW9kUmVnOEMuc2VzPC1lcyhNb2RSZWc4Qy50cyxtb2RlbD0iQU5OIixoPTEyLGhvbGRvdXQ9RkFMU0UsaW50ZXJ2YWxzPSJwYXJhbWV0cmljIiwNCiAgICAgICAgICAgICAgICBsZXZlbD0wLjk1LGNmVHlwZT0iTVNFIikNCk1vZFJlZzhDLmRlczwtZXMoTW9kUmVnOEMudHMsbW9kZWw9IkFBZE4iLGg9MTIsaG9sZG91dD1GQUxTRSxpbnRlcnZhbHM9InBhcmFtZXRyaWMiLA0KICAgICAgICAgICAgICAgIGxldmVsPTAuOTUsY2ZUeXBlPSJNU0UiKQ0KTW9kUmVnOEMuaHduPC1lcyhNb2RSZWc4Qy50cyxtb2RlbD0iQUFOIixoPTEyLGhvbGRvdXQ9RkFMU0UsaW50ZXJ2YWxzPSJwYXJhbWV0cmljIiwNCiAgICAgICAgICAgICAgICBsZXZlbD0wLjk1LGNmVHlwZT0iTVNFIikNCk1vZFJlZzhDLmh3YTwtZXMoTW9kUmVnOEMudHMsbW9kZWw9IkFBQSIsaD0xMixob2xkb3V0PUZBTFNFLGludGVydmFscz0icGFyYW1ldHJpYyIsDQogICAgICAgICAgICAgICAgbGV2ZWw9MC45NSxjZlR5cGU9Ik1TRSIpDQpNb2RSZWc4Qy5od208LWVzKE1vZFJlZzhDLnRzLG1vZGVsPSJBQU0iLGg9MTIsaG9sZG91dD1GQUxTRSxpbnRlcnZhbHM9InBhcmFtZXRyaWMiLA0KICAgICAgICAgICAgICAgIGxldmVsPTAuOTUsY2ZUeXBlPSJNU0UiKQ0KTW9kUmVnOEMuYXV0bzwtZXMoTW9kUmVnOEMudHMsbW9kZWw9IlpaWiIsaD0xMixob2xkb3V0PUZBTFNFLGludGVydmFscz0icGFyYW1ldHJpYyIsDQogICAgICAgICAgICAgICAgIGxldmVsPTAuOTUsY2ZUeXBlPSJNU0UiKQ0KYGBgDQpTZWFzb25hbCBtb2RlbHMgY2Fubm90IGJlIHVzZWQgZHVlIHRvIHRoZSBsYWNrIG9mIG9ic2VydmF0aW9ucyAoZGF0YSBzaXplIGlzIGp1c3QgNjArLCBjb21wYXJlZCB0byB0aGUgYmFja2Ryb3Agb2YgNTIgd2Vla3MgcGVyIHllYXIpLiBUaGVyZWZvcmUsIGBBQUFgIGFuZCBgQUFNYCB3aWxsIG5vdCBiZSB1c2VkLg0KV2UgYXJlIGxlZnQgd2l0aCBgQU5OYCwgYEFBZE5gIGFuZCBgQUFOYCBtb2RlbHMuDQpgYGB7cn0NCiNBTk4gbWVhbnMgQWRkaXRpdmUgZXJyb3JzLCBObyB0cmVuZCwgTm8gc2Vhc29uYWxpdHkgKFNFUykNCk1vZFJlZzhDLnNlcw0KYGBgDQoNCmBgYHtyfQ0KI0FBZE4gbWVhbnMgQWRkaXRpdmUgZXJyb3JzLCBBZGRpdGl2ZSAoZGFtcGxlZCkgdHJlbmQsIE5vIHNlYXNvbmFsaXR5IChERVMpDQpNb2RSZWc4Qy5kZXMNCmBgYA0KDQpgYGB7cn0NCiNBQU4gbWVhbnMgQWRkaXRpdmUgZXJyb3JzLCBBZGRpdGl2ZSB0cmVuZCwgTm8gc2Vhc29uYWxpdHkgKEhXIG5vIHNlYXNvbmFsaXR5KQ0KTW9kUmVnOEMuaHduDQpgYGANCg0KYGBge3J9DQojWlpaIG1lYW5zIGFsbCBjb21wb25lbnRzIGFyZSAoWikgZXN0aW1hdGVkIDIxDQpNb2RSZWc4Qy5hdXRvDQpgYGANCkl0IGNhbiBiZSBzZWVuIGFib3ZlIHRoYXQgdGhlIGBFVFMoQU5OKWAgbW9kZWwgcHJvZHVjZXMgdGhlIGxlYXN0IEFrYWlrZSBJbmZvcm1hdGlvbiBDcml0ZXJpb24gKEFJQykuIEl0IGNhbiBhbHNvIGJlIHNlZW4gdGhhdCBmb3IgYE1vZFJlZzhDLmF1dG9gLCBpdHMgZnVuY3Rpb24gcmV0dXJuZWQgYW4gYEVUUyhBTk4pYCBtb2RlbC4NCg0KIyMjIyBDaGVja2luZyBGb3JlY2FzdCBBY2N1cmFjeSB1c2luZyBNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IgKE1BUEUpDQpXZSB3aWxsIGNob29zZSB0aGUgYmVzdCBzbW9vdGhpbmcgbW9kZWwgYnkgY2hlY2tpbmcgdGhlIGxvd2VzdCB2YWx1ZS4NCmBgYHtyfQ0KTUFQRShNb2RSZWc4Qy50cyxNb2RSZWc4Qy5zZXMkZml0dGVkKQ0KTUFQRShNb2RSZWc4Qy50cyxNb2RSZWc4Qy5kZXMkZml0dGVkKQ0KTUFQRShNb2RSZWc4Qy50cyxNb2RSZWc4Qy5od24kZml0dGVkKQ0KTUFQRShNb2RSZWc4Qy50cyxNb2RSZWc4Qy5hdXRvJGZpdHRlZCkNCmBgYA0KRnJvbSB0aGUgcmVzdWx0cywgdGhlIEFkZGl0aXZlLCBObyB0cmVuZCwgTm8gc2Vhc29uYWxpdHkgKEFOTikgbW9kZWwgd2lsbCBiZSB1c2VkLg0KDQojIyMjIEZvcmVjYXN0aW5nIDQgV2Vla3MgVXNpbmcgdGhlIE1vZGVsIHdpdGggdGhlIExvd2VzdCBNQVBFDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQpmb3JlY2FzdChNb2RSZWc4Qy5hdXRvLGg9NCkNCnBsb3QoZm9yZWNhc3QoTW9kUmVnOEMuYXV0byxoPTQpKQ0KYGBgDQoNCiMjIyMgRm9yZWNhc3RpbmcgOCBXZWVrcw0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInfQ0KZm9yZWNhc3QoTW9kUmVnOEMuYXV0byxoPTgpDQpwbG90KGZvcmVjYXN0KE1vZFJlZzhDLmF1dG8saD04KSkNCmBgYA0KRnJvbSB0aGUgcmVzdWx0cywgYSBmbGF0IGhvcml6b250YWwgbGluZSBmb3JlY2FzdCBpcyBwcmVkaWN0ZWQuIFRoZSBob3Jpem9udGFsIGxpbmUgZm9yZWNhc3QgbWF5IGJlIGNhdXNlZCBkdWUgdG8gYmFja2xvZ3MgYW5kIG1pc3NpbmcgZGF0YS4gQWNjb3JkaW5nIHRvIGFuIGFydGljbGUgaW4gW1dvbHRlcnMgS2x1d2VyXShodHRwczovL3d3dy52YW5ndWFyZHN3LmNvbS9yZXNvdXJjZXMvYmxvZy1uZXdzbGV0dGVyL3N0cmFpZ2h0LWxpbmUtZm9yZWNhc3QtbXl0aC8pLCAiaW4gdGhlIGFic2VuY2Ugb2YgcmVhbCBwYXR0ZXJucywgdGhlIGJlc3QgZm9yZWNhc3QgbWlnaHQganVzdCBiZSBhIHN0cmFpZ2h0IGxpbmUuLi4uIFRoZSBlcnJvciBvbiBhIHN0cmFpZ2h0LWxpbmUgZm9yZWNhc3QgbWF5IGJlIGhpZ2hlciB0aGFuIHRoZSBlcnJvciB5b3Ugc2VlIG9uIG90aGVyIGZvcmVjYXN0czsgbm90IGJlY2F1c2UgdGhlIGZvcmVjYXN0IHdhcyBwb29yLCBidXQgYmVjYXVzZSB0aGUgaGlzdG9yaWNhbCBkYXRhIGlzIHRydWx5IHJhbmRvbS4iIEZvciBbQ2hhc2UsIDIwMTldKGh0dHBzOi8vYmxvZ3Muc2FzLmNvbS9jb250ZW50L3Nhc2NvbS8yMDE5LzA3LzI0L2hvdy1kby1pLWV4cGxhaW4tYS1mbGF0LWxpbmUtZm9yZWNhc3QtdG8tc2VuaW9yLW1hbmFnZW1lbnQvKSwgZmxhdC1saW5lIGZvcmVjYXN0cyBhcmUgZ2VuZXJhdGVkIHdoZW4gYSBnaXZlbiBkZW1hbmQgaGlzdG9yeS9kYXRhIGhhcyBubyBkZXRlY3RhYmxlIHRyZW5kIG9yIHNlYXNvbmFsaXR5LCB0aHVzIGp1c3QgcmVmbGVjdGluZyB0aGUgY3VycmVudCBkZW1hbmQgbGV2ZWwuDQoNCiMjIyMgTWFraW5nIGFuIEVUUyBVc2luZyB0aGUgRm9yZWNhc3QgTGlicmFyeQ0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLG91dC5oZWlnaHQ9JzEwMCUnLG91dC53aWR0aD0nMTAwJSd9DQpwbG90LnRzKFJlZzgkQ29uZmlybWVkWzI6NjNdLA0KICAgICAgICB5bGFiPSJDb25maXJtZWQiLA0KICAgICAgICB4bGFiPSJEYXRlIikNCmRhdGFjLm1vZGVsIDwtZXRzKHRzKFJlZzgkQ29uZmlybWVkWzI6NjNdLGZyZXF1ZW5jeT01MixzdGFydD1jKDIwMjAsMywxMSkpLG1vZGVsPSJaTk4iKQ0KZm9yZWNhc3QoZGF0YWMubW9kZWwsaD0zMCkNCnBsb3QoZm9yZWNhc3QoZGF0YWMubW9kZWwsaD0zMCksDQogICAgIG1haW49IkZvcmVjYXN0IG9uIHRoZSBOdW1iZXIgb2YgQ09WSUQtMTkgQ2FzZXMgaW4gUmVnaW9uIFZJSUkiLA0KICAgICBjb2w9InJlZCIsDQogICAgIHhsYWI9IkRhdGUiLA0KICAgICB5bGFiPSJOdW1iZXIgb2YgQ2FzZXMiKQ0KYGBgDQoNCiMjIEZvcmVjYXN0aW5nIHdpdGggQVJJTUENCg0KIyMjIEZvcmVjYXN0aW5nIHRoZSBDb25maXJtZWQgQ2FzZXMgVXNpbmcgQVJJTUENCg0KIyMjIyBBdWdtZW50ZWQgRGlja2V5LUZ1bGxlciBUZXN0IChBREYpIHRvIERldGVybWluZSB0aGUgKmQqLXZhbHVlDQpgYGB7cn0NCmFkZi50ZXN0KE1vZFJlZzhDLnRzLCBhbHRlcm5hdGl2ZT0ic3RhdGlvbmFyeSIpDQphZGYudGVzdChkaWZmKE1vZFJlZzhDLnRzKSwgYWx0ZXJuYXRpdmU9InN0YXRpb25hcnkiKQ0KYGBgDQpUaGUgQURGIFRlc3QgcmVzdWx0IGZvciBgTW9kUmVnOEMudHNgIHN1Z2dlc3RzIG5vbi1zdGF0aW9uYXJpdHkgd2hpbGUgdGhlIHJlc3VsdCBmb3IgYGRpZmYoTW9kUmVnOEMudHMpYCBzdWdnZXN0cyBzdGF0aW9uYXJpdHksIGhpbnRpbmcgdGhhdCBgZD0xYC4gVGhpcyBpcyBkdWUgdG8gdGhlIHAtdmFsdWUgZm9yIGBNb2RSZWc4Qy50c2AgYmVpbmcgaGlnaGVyIHRoYW4gJFxhbHBoYSA9IDAuMDUkIHdoaWxlIHRoZSBwLXZhbHVlIGZvciBgZGlmZihNb2RSZWc4Qy50cylgIGlzIGxvd2VyIHRoYW4gJFxhbHBoYSQsIHN1Z2dlc3RpbmcgdGhlIHJlamVjdGlvbiBvZiB0aGUgbnVsbCBoeXBvdGhlc2lzIG9mIHVuaXQgcm9vdCBwcmVzZW5jZSAoc3RhdGlvbmFyaXR5KS4NCg0KIyMjIyBWaXN1YWxpemluZyBBQ0YgYW5kIFBhcnRpYWwgQUNGDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsb3V0LmhlaWdodD0nMTAwJScsb3V0LndpZHRoPScxMDAlJ30NCnBsb3QoTW9kUmVnOEMudHMpDQphY2YoTW9kUmVnOEMudHMpDQpwYWNmKE1vZFJlZzhDLnRzKQ0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCnBsb3QoZGlmZihNb2RSZWc4Qy50cykpDQphY2YoZGlmZihNb2RSZWc4Qy50cykpDQpwYWNmKGRpZmYoTW9kUmVnOEMudHMpKQ0KYGBgDQoNCg0KIyMjIyBDcmVhdGluZyBhbiBBUklNQSBNb2RlbCBNYW51YWxseSB1c2luZyBgYXJpbWEoKWANCmBgYHtyfQ0KY29uZi5hcmltYSA8LSBhcmltYShNb2RSZWc4Qy50cyxvcmRlcj1jKDAsMSwxKSkNCmNvbmYuYXJpbWENCmBgYA0KDQpgYGB7cn0NCmNvbmYucHJlZCA8LSBmb3JlY2FzdChjb25mLmFyaW1hLCBoID0gNDgsIGxldmVsPWMoOTcuNSkpDQpjb25mLnByZWQNCmBgYA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQpwbG90KGNvbmYucHJlZCkNCmBgYA0KDQojIyMjIEF1dG9tYXRpYyBBUklNQSBNb2RlbCBEZXRlcm1pbmF0aW9uIHVzaW5nIGBhdXRvLmFyaW1hKClgDQpgYGB7cn0NCmNvbmYuYWFyaW1hPC1hdXRvLmFyaW1hKE1vZFJlZzhDLnRzLG1heC5vcmRlciA9IDEyLHRyYWNlPVRSVUUsc2Vhc29uYWw9RkFMU0UpDQpjb25mLmFhcmltYQ0KYGBgDQojIyMjIFNwZWNpZnlpbmcgdGhlIEluZm9ybWF0aW9uIENyaXRlcmlvbiAoSUMpIGluIGBhdXRvLmFyaW1hYCB0byBmaW5kIGBwYCxgZGAsIGFuZCBgcWANCmBgYHtyfQ0KYXV0b2FpYyA8LSBhdXRvLmFyaW1hKFJlZzgkQ29uZmlybWVkWzI6NjNdLGljPSdhaWMnKQ0KYXV0b2FpYw0KYGBgDQoNCmBgYHtyfQ0KYXV0b2JpYyA8LSBhdXRvLmFyaW1hKFJlZzgkQ29uZmlybWVkWzI6NjNdLGljPSdiaWMnKQ0KYXV0b2JpYw0KYGBgDQoNCmBgYHtyfQ0KYXV0b2FpY2MgPC0gYXV0by5hcmltYShSZWc4JENvbmZpcm1lZFsyOjYzXSxpYz0nYWljYycpDQphdXRvYWljYw0KYGBgDQpBbGwgdGhyZWUgSUNzIHN1Z2dlc3RzIGEgYHAsZCxxYCBvcmRlciBvZiBgMCwxLDBgLCB3aGljaCBpcyBzaW1pbGFyIHRvIHRoZSBgYXV0by5hcmltYWAtc3VnZ2VzdGVkIG1vZGVsIHdpdGhvdXQgc3BlY2lmeWluZyB0aGUgSUMuDQoNClRoZSBzZWNvbmQgbWV0aG9kIGF1dG9tYXRpY2FsbHkgY2hlY2tzIHRoZSBBUklNQSB3aXRoIHRoZSBsb3dlc3QgQUlDLCBCSUMsIGFuZCBBSUNDIHJlc3BlY3RpdmVseS4gQWxsIGFncmVlZCB0aGF0IHRoZSBsb3dlc3Qgd2FzIEFSSU1BKDAsMSwwKSB3aGljaCBhbHNvIGNvaW5jaWRlcyB3aXRoIHRoZSByZXN1bHQgb2YgdGhlIGZpcnN0IG1ldGhvZC4NCg0KYGBge3J9DQpjb25mLnByZWQgPC0gZm9yZWNhc3QoY29uZi5hYXJpbWEsIGggPSA0OCwgbGV2ZWw9Yyg5Ny41KSkNCmNvbmYucHJlZA0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCnBsb3QoY29uZi5wcmVkKQ0KYGBgDQpIZXJlLCB3ZSBzZWUgdGhlIEFSSU1BIEVUUyBncmFwaCBhbmQgZm9yZWNhc3QgdXNpbmcgQVJJTUEoMCwxLDApLiBTaW1pbGFyIHRvIHRoZSBmb3JlY2FzdCBlYXJsaWVyIGZyb20gZXhwb25lbnRpYWwgc21vb3RoaW5nICwgYSBob3Jpem9udGFsIGxpbmUgZm9yZWNhc3QgaXMgc2Vlbi4NCg0KIyMgQ3JlYXRpbmcgYSBTaW1wbGUgTWFwIFBsb3QgVXNpbmcgdGhlIGBMZWFmbGV0YCBSIFBhY2thZ2UNCkluIGNyZWF0aW5nIGEgc2ltcGxlIG1hcCBwbG90LCBvbmUgY2FuIHVzZSB0aGUgYExlYWZsZXRgIHBhY2thZ2UuIGBMZWFmbGV0YCBpcyBhbiAib3Blbi1zb3VyY2UsIEphdmFTY3JpcHQgbGlicmFyeSBmb3IgaW50ZXJhY3RpdmUgbWFwcyIgW2h0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9dLiBgTGVhZmxldGAgY2FuIGNyZWF0ZSBtYXBzIHRoYXQgY2FuIGJlIHVzZWQgZGlyZWN0bHkgZnJvbSB0aGUgUiBjb25zb2xlLCBSU3R1ZGlvLCBhbmQgUiBNYXJrZG93biBkb2N1bWVudHMgW2h0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9sZWFmbGV0L2luZGV4Lmh0bWxdLiBUaGUgcGFja2FnZSBoYXMgdGhlIGNhcGFiaWxpdHkgdG8gbWFwIHBvbHlnb25zLCBHZW9KU09OOyBjcmVhdGUgbWFwIGxheWVycywgYWRkIG1hcCBsYWJlbHMsIGFuZCBtYW55IG1vcmUuDQpgYGB7cn0NCmxpYnJhcnkobGVhZmxldCkNCmBgYA0KDQoNCkhlcmUsIGBMZWFmbGV0YCBpcyB1c2VkIHRvIGNyZWF0ZSBhIHNpbXBsZSBtYXAgcGxvdCBmb3IgdGhlIENPVklELTE5IFN0YXRpc3RpY3Mgb2YgRWFzdGVybiBWaXNheWFzIChSZWdpb24gVklJSSkuIFRvIHBsb3QgdGhlIGJvcmRlcnMgb2YgdGhlIHJlZ2lvbiwgb25lIGNhbiBtYW51YWxseSBlbnRlciB0aGUgbGVuZ3RocyBvZiB0aGUgYm9yZGVyIHRvIGNyZWF0ZSBhIHBvbHlnb24sIG9yIGRvd25sb2FkIGEgc2hhcGVmaWxlL0dlb0pTT04vVG9wb0pTT04gY29udGFpbmluZyB0aGUgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlZ2lvbidzIGJvcmRlciBsZW5ndGhzLiBJbiBvdXIgY2FzZSwgd2UgdXNlZCBhIEdlb0pTT04gZmlsZSBvYnRhaW5lZCBmcm9tIFtPcGVuU3RyZWV0TWFwXShodHRwOi8vcG9seWdvbnMub3BlbnN0cmVldG1hcC5mci8/aWQ9Mzc1OTE5MykuIFRoZSBPU00gUmVsYXRpb24gSUQgZm9yIEVhc3Rlcm4gVmlzYXlhcyBpcyBbMzc1OTE5M10oaHR0cHM6Ly9ub21pbmF0aW0ub3BlbnN0cmVldG1hcC5vcmcvdWkvZGV0YWlscy5odG1sP29zbXR5cGU9UiZvc21pZD0zNzU5MTkzJmNsYXNzPWJvdW5kYXJ5KS4NCg0KVGhlIGNvZGVzIHVzZWQgYmVsb3cgd2VyZSBndWlkZWQgYnkgdGhlIHR1dG9yaWFscy9ndWlkZXMgZnJvbSBbRWFydGggTGFiXShodHRwczovL3d3dy5lYXJ0aGRhdGFzY2llbmNlLm9yZy9jb3Vyc2VzL2VhcnRoLWFuYWx5dGljcy9nZXQtZGF0YS11c2luZy1hcGlzL2xlYWZsZXQtci8pLCBhbiBSU3R1ZGlvIFB1YnMgZ3VpZGUgYnkgW01hdHQgRHJheV0oaHR0cHM6Ly9yc3R1ZGlvLXB1YnMtc3RhdGljLnMzLmFtYXpvbmF3cy5jb20vMzU5ODExX2VmNTVjNmJkMDhjZTQwYThiODJiNTcyNzRhOTZiYmIwLmh0bWwpLCBhIGd1aWRlIGJ5IFtKZXJlbXkgT2FrbGV5XShodHRwOi8vd3d3LmplcmVteS1vYWtsZXkuc3RhZmYuc2hlZi5hYy51ay9tYXM2MTAwNC9FREF0dXRvcmlhbC9tYXBzLXdpdGgtbGVhZmxldC5odG1sKSBhbmQgZnJvbSB0aGUgb2ZmaWNpYWwgUlN0dWRpbyBndWlkZSBieSB0aGUgY3JlYXRvcnMgb2YgW0xlYWZsZXRdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8pLg0KYGBge3IsIG91dC53aWR0aCA9ICcxMDAlJ30NCmxhYlI4PC1wYXN0ZShzZXA9IjwvYnI+IiwNCiAgICAgICAgICAgIjxiPkVhc3Rlcm4gVmlzYXlhcyBDT1ZJRC0xOSBTdGF0aXN0aWNzOjwvYj4iLA0KICAgICAgICAgICAiKEFzIG9mIE1heSAxMywyMDIxKSIsDQogICAgICAgICAgIHBhc3RlMCgiQ29uZmlybWVkIENhc2VzOiIsc3VtKFJlZzgkQ29uZmlybWVkKSksDQogICAgICAgICAgIHBhc3RlMCgiQWN0aXZlIENhc2VzOiIsTlJlZzgkQWN0aXZlX3RvdGFsK1JlZzgkQ29uZmlybWVkWzY0XSksDQogICAgICAgICAgIHBhc3RlMCgiRGVhdGhzOiIsTlJlZzgkRGVhdGhfdG90YWwpLA0KICAgICAgICAgICBwYXN0ZTAoIlJlY292ZXJlZDoiLE5SZWc4JFJlY292ZXJlZF90b3RhbCkpDQoNCm50b3BvPC1yZ2RhbDo6cmVhZE9HUigiRVZpc2F5YXMuZ2VvanNvbiIpDQpsZWFmbGV0KG50b3BvLCB3aWR0aD0nMTAwJScpJT4lDQogIGFkZFRpbGVzKCklPiUNCiAgYWRkUG9seWdvbnMoc3Ryb2tlPVRSVUUsDQogICAgICAgICAgICAgIHNtb290aEZhY3RvciA9IDAuMywNCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjAwNSklPiUNCiAgYWRkUG9wdXBzKGxuZz0xMjUsbGF0PTExLjI0LGxhYlI4LA0KICAgICAgICAgICAgb3B0aW9ucz1wb3B1cE9wdGlvbnMoY2xvc2VCdXR0b24gPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0aW9uPSJGcm9tIDxhIGhyZWY9J2h0dHBzOi8vZG9oLmdvdi5waC9jb3ZpZDE5dHJhY2tlcic+RE9IIENPVklELTE5IFRyYWNrZXI8L2E+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsb3NlT25DbGljayA9IEZBTFNFKSkNCmBgYA0K